Criando Stored Procedures e Functions no MySQL 8.4
Introdução
Stored Routines (Rotinas Armazenadas) no MySQL são conjuntos de uma ou mais instruções SQL pré-compiladas e armazenadas no banco de dados, que podem ser executadas sob demanda. Elas vêm em duas formas principais:
- Stored Procedures (Procedimentos Armazenados): Executam um conjunto de ações, podem receber parâmetros de entrada (
IN), retornar parâmetros de saída (OUT) ou modificar parâmetros de entrada/saída (INOUT). São invocados usando a instruçãoCALL. - Stored Functions (Funções Armazenadas): Executam um conjunto de ações e obrigatoriamente retornam um único valor de um tipo específico. Podem receber apenas parâmetros de entrada (
IN). São invocadas como parte de uma expressão SQL (por exemplo, em umSELECTouSET).
Vantagens de usar Stored Routines:
- Reutilização de Código: Escreva a lógica uma vez e chame-a várias vezes.
- Melhor Desempenho: O código é pré-compilado e armazenado no servidor, reduzindo o tráfego de rede e o overhead de análise.
- Segurança Aprimorada: Conceda permissões para executar a rotina sem dar acesso direto às tabelas subjacentes (usando
SQL SECURITY DEFINER). - Abstração da Lógica de Negócios: Encapsula regras de negócios complexas no banco de dados.
- Consistência: Garante que a mesma lógica seja aplicada sempre que a rotina for chamada.
Sintaxe Geral
A sintaxe básica para criar procedimentos e funções é:
Para Procedures:
CREATE
[DEFINER = user]
PROCEDURE [IF NOT EXISTS] nome_procedimento ([parametro_proc[,...]])
[caracteristica ...]
corpo_rotinaPara Functions:
CREATE
[DEFINER = user]
FUNCTION [IF NOT EXISTS] nome_funcao ([parametro_func[,...]])
RETURNS tipo_retorno
[caracteristica ...]
corpo_rotinaComponentes da Sintaxe:
parametro_proc:[ IN | OUT | INOUT ] nome_parametro tipo_dadoparametro_func:nome_parametro tipo_dadotipo_dado: Qualquer tipo de dado válido do MySQL (ex:INT,VARCHAR(50),DATE,DECIMAL(10,2)).tipo_retorno: (Apenas paraFUNCTION) O tipo de dado do valor que a função retornará.caracteristica: Opções que definem o comportamento e as propriedades da rotina:{ COMMENT 'string_comentario' | LANGUAGE SQL | [NOT] DETERMINISTIC | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } | SQL SECURITY { DEFINER | INVOKER } }corpo_rotina: Uma instrução SQL válida ou um blocoBEGIN ... ENDcontendo múltiplas instruções SQL, declarações de variáveis, loops, condicionais, etc.
Explicação Detalhada dos Componentes
CREATE PROCEDURE / CREATE FUNCTION
- Instruções usadas para criar uma nova rotina armazenada.
- Por padrão, a rotina é associada ao banco de dados atual. Para associá-la explicitamente a outro banco, use
nome_banco.nome_rotina. CREATE FUNCTIONtambém é usado para criar Loadable Functions (Funções Carregáveis), que são funções externas escritas em C/C++ e carregadas no servidor. Funções armazenadas e carregáveis compartilham o mesmo namespace.
DEFINER = user
- Opcional. Especifica a conta MySQL (
'user_name'@'host_name',CURRENT_USER, ouCURRENT_USER()) cujos privilégios serão verificados durante a execução da rotina, se a característicaSQL SECURITY DEFINERestiver ativa (que é o padrão). - Se omitido, o
DEFINERpadrão é o usuário que executa a instruçãoCREATE. - A conta
DEFINERprecisa ter os privilégios necessários para executar as instruções dentro do corpo da rotina seSQL SECURITYforDEFINER. - Os privilégios necessários para definir um
DEFINERespecífico dependem dos privilégios do usuário que está criando a rotina (veja Seção 27.6 do manual).
IF NOT EXISTS
- Opcional. Evita que um erro ocorra se já existir uma rotina (procedure ou function) com o mesmo nome.
- Se uma rotina com o mesmo nome já existe:
- A instrução
CREATE ... IF NOT EXISTSé ignorada (com um aviso se for uma função com nome igual a uma função nativa ou carregável). - Sem
IF NOT EXISTS, um erro ocorreria.
- A instrução
Lista de Parâmetros (...)
- Obrigatória, mesmo que vazia (
()). - Nomes de parâmetros não são sensíveis a maiúsculas/minúsculas.
- Para Procedures (
parametro_proc):IN: (Padrão) Passa um valor para dentro do procedimento. Modificações no valor dentro do procedimento não são visíveis para quem chamou após o retorno.OUT: Passa um valor para fora do procedimento (retorno). O valor inicial dentro do procedimento éNULL. O valor final é visível para quem chamou. Requer uma variável de usuário (ex:@variavel) ou outra variável válida na chamadaCALL.INOUT: CombinaINeOUT. O valor é inicializado por quem chama, pode ser modificado pelo procedimento, e a modificação é visível para quem chamou após o retorno. Requer uma variável na chamadaCALL.
- Para Functions (
parametro_func):- Todos os parâmetros são implicitamente
IN. Não se pode usarOUTouINOUT.
- Todos os parâmetros são implicitamente
RETURNS tipo_retorno
- Obrigatório apenas para
FUNCTION. - Define o tipo de dado do valor único que a função retornará.
- O corpo da função (
corpo_rotina) deve conter uma instruçãoRETURN valor. - Se o valor retornado por
RETURNfor de um tipo diferente dotipo_retorno, o MySQL tentará converter (coagir) o valor para o tipo correto.
caracteristica (Characteristics)
Define propriedades importantes da rotina:
COMMENT 'string_comentario': (Extensão MySQL) Adiciona um comentário descritivo à rotina, visível comSHOW CREATE PROCEDURE/SHOW CREATE FUNCTION.LANGUAGE SQL: Indica que a rotina é escrita em SQL. Atualmente, o MySQL só suportaLANGUAGE SQL, então esta cláusula é informativa e ignorada.[NOT] DETERMINISTIC:DETERMINISTIC: Indica que a rotina sempre produz o mesmo resultado para os mesmos parâmetros de entrada. Exemplos: Uma função que calculaa + b.NOT DETERMINISTIC: (Padrão) Indica que a rotina pode produzir resultados diferentes para os mesmos parâmetros de entrada. Exemplos: Funções que usamNOW(),RAND(), ou que leem/modificam dados em tabelas (pois os dados podem mudar entre chamadas).- Importância:
- O MySQL confia na declaração do criador (não verifica internamente).
- Declarar incorretamente pode afetar a otimização de consultas e a segurança da replicação binária.
- Funções
NOT DETERMINISTICpodem exigir o privilégioSUPERse o log binário estiver ativo (veja Seção 27.7 do manual).
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }:- Estas características são informativas no MySQL; o servidor não as usa para restringir o que a rotina pode fazer.
CONTAINS SQL: (Padrão) A rotina não lê nem escreve dados (ex:SET @x = 1).NO SQL: A rotina não contém nenhuma instrução SQL.READS SQL DATA: A rotina contém instruções que leem dados (ex:SELECT), mas não escrevem.MODIFIES SQL DATA: A rotina contém instruções que podem escrever dados (ex:INSERT,DELETE,UPDATE).
SQL SECURITY { DEFINER | INVOKER }:- Define o contexto de segurança sob o qual a rotina será executada.
DEFINER: (Padrão) A rotina executa com os privilégios do usuário especificado na cláusulaDEFINER. Quem chama precisa apenas do privilégioEXECUTEsobre a rotina.INVOKER: A rotina executa com os privilégios do usuário que a invocou (CALLouSELECT). Quem chama precisa do privilégioEXECUTEsobre a rotina e dos privilégios necessários para executar as instruções dentro do corpo da rotina.
corpo_rotina (Routine Body)
- Contém o código SQL da rotina.
- Pode ser uma única instrução SQL (ex:
SELECT * FROM tabela;ouRETURN a + b;). - Pode ser um bloco composto
BEGIN ... END;para múltiplas instruções. - Blocos
BEGIN...ENDpodem conter:- Declarações de variáveis locais (
DECLARE). - Cursores (
DECLARE CURSOR). - Manipuladores de condição (
DECLARE HANDLER). - Estruturas de controle (loops
WHILE,REPEAT,LOOP; condicionaisIF,CASE).
- Declarações de variáveis locais (
- Restrições:
- Stored Functions não podem conter instruções que realizem
COMMITouROLLBACKexplícito ou implícito. Procedures podem. - Stored Functions não podem retornar conjuntos de resultados diretamente (ex:
SELECT * FROM tabela;). UseSELECT ... INTO variavel;. Procedures podem retornar result sets. - Instruções
USE nome_banco;não são permitidas dentro de rotinas. A rotina opera implicitamente no banco de dados ao qual está associada. Use nomes qualificados (nome_banco.nome_tabela) para acessar outros bancos. - Não se pode referenciar parâmetros ou variáveis locais de rotina dentro de Prepared Statements criados na mesma rotina.
- Veja a Seção 27.8 do manual para mais restrições.
- Stored Functions não podem conter instruções que realizem
Contexto de Segurança (DEFINER vs INVOKER)
SQL SECURITY DEFINER(Padrão):- Quem executa? Os privilégios da conta
DEFINER. - Privilégios necessários para chamar:
EXECUTEna rotina. - Privilégios verificados na execução: A conta
DEFINERprecisa ter os privilégios para as ações dentro da rotina. - Caso de uso: Permitir que usuários com poucos privilégios executem ações controladas e seguras definidas por um usuário mais privilegiado (
DEFINER). CURRENT_USER()dentro da rotina: Retorna a contaDEFINER.
- Quem executa? Os privilégios da conta
SQL SECURITY INVOKER:- Quem executa? Os privilégios do usuário que chamou a rotina.
- Privilégios necessários para chamar:
EXECUTEna rotina e os privilégios para as ações dentro da rotina. - Privilégios verificados na execução: Os privilégios do usuário que chamou são verificados para cada instrução dentro da rotina.
- Caso de uso: Criar rotinas genéricas cujo acesso aos dados depende de quem as está chamando.
CURRENT_USER()dentro da rotina: Retorna a conta do usuário que chamou.
Nota sobre Roles: Por padrão, ao executar uma rotina DEFINER, apenas os default roles da conta DEFINER são ativados, a menos que activate_all_roles_on_login esteja habilitado. Se a execução depender de privilégios de roles não padrão, use SET ROLE dentro da rotina (com cautela).
Privilégios Necessários
- Para Criar: Requer o privilégio
CREATE ROUTINE. Se a cláusulaDEFINERfor usada para especificar um usuário diferente, podem ser necessários privilégios adicionais (geralmenteSUPERou privilégios específicos dependendo da conta definida). - Para Modificar: Requer o privilégio
ALTER ROUTINE. - Para Executar: Requer o privilégio
EXECUTE. - Outorgados Automaticamente: Por padrão (
automatic_sp_privileges=ON), o criador da rotina recebe automaticamente os privilégiosALTER ROUTINEeEXECUTEsobre a rotina criada. - Log Binário: Se o log binário estiver ativo,
CREATE FUNCTIONpode exigir o privilégioSUPERse a função não for declarada comoDETERMINISTICouREADS SQL DATA/NO SQL.
Considerações Importantes
- SQL Mode: O MySQL armazena o
sql_modeativo no momento da criação/alteração da rotina e a executa sempre com essesql_mode, independentemente dosql_modeda sessão que a invoca. A mudança de modo ocorre após a avaliação dos argumentos passados. - Conflitos de Nomes: Se você criar uma rotina com o mesmo nome de uma função nativa do MySQL, use um espaço entre o nome e os parênteses ao definir e ao chamar (
nome_rotina (...)) para evitar erro de sintaxe. É melhor evitar esses nomes. - Manipulação de Tipos de Dados: O MySQL verifica tipos de dados e estouro (
overflow) em atribuições a parâmetros e variáveis locais. ColunasCHARACTER SETeCOLLATEpodem ser especificadas; caso contrário, usam-se os padrões do banco de dados no momento da criação da rotina. DELIMITER: Ao definir rotinas no clientemysqlque contêm ponto e vírgula (;) internamente (especialmente com blocosBEGIN...END), você precisa mudar o delimitador padrão do cliente para algo diferente (ex://ou$$) antes doCREATEe restaurá-lo depois doEND.
DELIMITER //
CREATE PROCEDURE nome_procedimento()
BEGIN
-- Várias instruções SQL aqui;
SELECT * FROM tabela;
INSERT INTO outra_tabela (coluna) VALUES (1);
END // -- Usa o novo delimitador
DELIMITER ; -- Restaura o delimitador padrãoExemplos Práticos
(Usando a tabela accounts do tutorial como exemplo)
Tabela accounts:
CREATE TABLE accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
balance DECIMAL(10, 2)
);
INSERT INTO accounts (name, balance) VALUES
('Cliente A', 1300.00),
('Cliente B', 2500.50),
('Cliente C', 800.75);1. Stored Procedure Sem Parâmetros
Busca todos os dados da tabela accounts.
DELIMITER //
CREATE PROCEDURE fetchData()
BEGIN
SELECT * FROM accounts;
END //
DELIMITER ;
-- Chamada:
CALL fetchData();2. Stored Procedure Com Parâmetro IN
Busca os dados de uma conta específica pelo ID.
DELIMITER //
CREATE PROCEDURE fetch_data_in(IN uid INT)
BEGIN
SELECT * FROM accounts WHERE id = uid;
END //
DELIMITER ;
-- Chamada:
CALL fetch_data_in(1);
CALL fetch_data_in(3);3. Stored Procedure Com Parâmetro OUT
Calcula e retorna o saldo total de todas as contas.
DELIMITER //
CREATE PROCEDURE fetchDataOut(OUT totBalance DECIMAL(12, 2))
BEGIN
-- Note que SELECT INTO é necessário para atribuir a variável OUT
SELECT SUM(balance) INTO totBalance FROM accounts;
END //
DELIMITER ;
-- Chamada:
CALL fetchDataOut(@balance); -- @balance é uma variável de sessão para receber o valor
SELECT @balance; -- Mostra o valor retornado(Exemplo alternativo do Manual) Contar cidades por país:
DELIMITER //
CREATE PROCEDURE citycount (IN country CHAR(3), OUT cities INT)
BEGIN
SELECT COUNT(*) INTO cities FROM world.city -- Assume tabela world.city
WHERE CountryCode = country;
END //
DELIMITER ;
-- Chamada:
CALL citycount('JPN', @cities_jpn);
SELECT @cities_jpn;4. Stored Procedure Com Parâmetro INOUT
Atualiza o saldo de uma conta ao sacar um valor, retornando o novo saldo.
DELIMITER //
CREATE PROCEDURE update_data(IN uid INT, INOUT amount DECIMAL(10, 2))
BEGIN
DECLARE uBalance DECIMAL(10, 2);
-- Pega o saldo atual
SELECT balance INTO uBalance FROM accounts WHERE id = uid;
-- Verifica se há saldo suficiente
IF amount > uBalance THEN
-- Sinaliza um erro customizado se não houver saldo
SIGNAL SQLSTATE '45000' -- SQLSTATE genérico para erro do usuário
SET MESSAGE_TEXT = 'Saldo insuficiente';
ELSE
-- Atualiza o saldo
UPDATE accounts
SET balance = uBalance - amount
WHERE id = uid;
-- Seleciona o NOVO saldo e o coloca na variável INOUT 'amount' para retorno
SELECT balance INTO amount FROM accounts WHERE id = uid;
END IF;
END //
DELIMITER ;
-- Chamada:
SET @withdraw_amount = 100.00; -- Define o valor a sacar
CALL update_data(1, @withdraw_amount); -- Passa o ID e a variável
SELECT @withdraw_amount; -- Mostra o NOVO saldo retornado na variável5. Stored Function Simples
Cria uma função que retorna uma saudação.
-- Não precisa de DELIMITER aqui, pois não há ';' interno
CREATE FUNCTION hello (s CHAR(20))
RETURNS CHAR(50)
DETERMINISTIC -- Importante: esta função sempre retorna o mesmo para a mesma entrada
RETURN CONCAT('Hello, ', s, '!');
-- Chamada (dentro de uma expressão):
SELECT hello('world');Invocando Stored Routines
- Procedures: Use a instrução
CALL.CALL nome_procedimento(parametro_in, @variavel_out, @variavel_inout); - Functions: Referencie a função diretamente em uma expressão SQL onde um valor é esperado.
SELECT nome_funcao(parametro_in) AS resultado; SET @valor = nome_funcao(parametro_in); SELECT * FROM tabela WHERE coluna = nome_funcao(parametro_in);
Este guia abrange os principais aspectos da criação de Stored Procedures e Functions no MySQL 8.4, combinando a precisão do manual oficial com a praticidade dos exemplos do tutorial. Lembre-se de consultar o manual do MySQL para obter detalhes mais aprofundados sobre casos específicos e restrições.
